#ifndef UDP_COMMUNICATOR_H
#define UDP_COMMUNICATOR_H

// #define WIFI_MODE "STA"  // "STA" or "AP"

#include <EEPROM.h>
#include <WiFi.h>
#include <WiFiUdp.h>

#define SSID_LENGTH 32
#define PASSWORD_LENGTH 63
#define REMOTE_IP_LENGTH 16
#define SSID_OFFSET 0
#define PASSWORD_OFFSET SSID_OFFSET+SSID_LENGTH

#define IP_OFFSET PASSWORD_OFFSET+PASSWORD_LENGTH
#define EEPROM_LENGTH SSID_LENGTH+PASSWORD_LENGTH+REMOTE_IP_LENGTH

// WiFiUDP udp;
class UDP_Communicator : public Print {
    public:
        UDP_Communicator(WiFiUDP * );
        void set_remote_ip(const char*);
        void set_ssid(const char*);
        void set_password(const char*);
        bool wifi_startup();

        void save_SSID();
        void load_SSID();
        void save_PSWD();
        void load_PSWD();
        void save_rIP();
        void load_rIP();
        void log_output(Print *);
        void log_output_udp();
        // void set_remote_port(int);
        void write_string(String);
        void broadcast_string(String);
        // void printf(const char*, ...);
        
        // void println(const char*);
        // void print(const char*);
        size_t write(uint8_t) override;
        size_t write(const uint8_t *buffer, size_t size) override;
        // size_t print(const char *);
        bool connected = false;
        char* get_ssid();
        char* get_password();
        char* get_remote_ip();
        char local_IP[16];
    private: 
        char remote_IP[16];
        char ssid[32];
        char password[63];
        WiFiUDP * udp;
};

UDP_Communicator :: UDP_Communicator(WiFiUDP * udp){
    this->udp = udp;
}

void UDP_Communicator :: set_remote_ip(const char* new_content){
    strcpy(this->remote_IP, new_content);
    this->save_rIP();
}
void UDP_Communicator :: set_ssid(const char* new_content){
    strcpy(this->ssid, new_content);
    this->save_SSID();
}
void UDP_Communicator :: set_password(const char* new_content){
    strcpy(this->password, new_content);
    this->save_PSWD();
}

bool UDP_Communicator :: wifi_startup(){
    WiFi.begin(this->ssid, this->password);
    for (int i=0; i<5; i++){
        if (WiFi.status() != WL_CONNECTED){
            Serial.print("Connecting to WiFi ");
            Serial.print(this->ssid);
            Serial.println(" ...");
            vTaskDelay(1000);
        }else{
            this->connected = true;
            Serial.println("Connected");
            break;
        }
    }
    // while (WiFi.status() != WL_CONNECTED){
    //     vTaskDelay(1000);
    //     Serial.println("Connecting to WiFi...");
    // }
    // 开始UDP通信
    if (WiFi.status() == WL_CONNECTED){
        this->udp->begin(COMMUNICATE_PORT);
        strcpy(this->local_IP, WiFi.localIP().toString().c_str());
        Serial.print("UDP server started at ");
        Serial.print(this->local_IP);
        Serial.print(":");
        Serial.println(COMMUNICATE_PORT);
        return true;
    }else{
        Serial.println("connection failed");
        return false;
    }
}

void UDP_Communicator :: write_string(String content){
    this->udp->beginPacket(this->remote_IP, COMMUNICATE_PORT);
    this->udp->write(reinterpret_cast<const uint8_t*>(content.c_str()), content.length());
    this->udp->endPacket();
}

void UDP_Communicator :: broadcast_string(String content){
    this->udp->beginPacket(IPAddress(255, 255, 255, 255), COMMUNICATE_PORT);  // 广播地址
    this->udp->write(reinterpret_cast<const uint8_t*>(content.c_str()), content.length());
    this->udp->endPacket();
}


void UDP_Communicator :: save_SSID(){
    EEPROM.begin(EEPROM_LENGTH);
    for (int i = 0; i < SSID_LENGTH; ++i) {
        EEPROM.write(SSID_OFFSET + i, this->ssid[i]);
    }
    EEPROM.commit();
}

void UDP_Communicator :: load_SSID() {
    EEPROM.begin(EEPROM_LENGTH);
    for (int i = 0; i < SSID_LENGTH; ++i) {
        this->ssid[i] = EEPROM.read(SSID_OFFSET + i);
    }
}

void UDP_Communicator :: save_PSWD() {
    EEPROM.begin(EEPROM_LENGTH);
    for (int i = 0; i < PASSWORD_LENGTH; ++i) {
        EEPROM.write(PASSWORD_OFFSET + i, this->password[i]);
    }
    EEPROM.commit();
}

void UDP_Communicator :: load_PSWD() {
    EEPROM.begin(EEPROM_LENGTH);
    for (int i = 0; i < PASSWORD_LENGTH; ++i) {
        this->password[i] = EEPROM.read(PASSWORD_OFFSET + i);
    }
}

void UDP_Communicator :: save_rIP() {
    EEPROM.begin(EEPROM_LENGTH);
    for (int i = 0; i < REMOTE_IP_LENGTH; ++i) {
        EEPROM.write(IP_OFFSET + i, this->remote_IP[i]);
        // Serial.printf("%d: %c\n", IP_OFFSET + i, this->remote_IP[i]);
    }
    EEPROM.commit();
}

void UDP_Communicator :: load_rIP() {
    EEPROM.begin(EEPROM_LENGTH);
    for (int i = 0; i < REMOTE_IP_LENGTH; ++i) {
        // Serial.printf("%d: %c\n", IP_OFFSET + i, EEPROM.read(IP_OFFSET + i));
        this->remote_IP[i] = EEPROM.read(IP_OFFSET + i);
    }
}


void UDP_Communicator :: log_output(Print * serial) {
    serial->printf("ssid: %s\npassword: %s\nremote IP: %s\nlocal IP: %s\n", 
                    this->ssid, this->password, this->remote_IP, this->local_IP);
}

void UDP_Communicator :: log_output_udp() {
    char log_c_buffer[EEPROM_LENGTH+32];
    sprintf(log_c_buffer, "ssid: %s\npassword: %s\nremote IP: %s\nlocal IP: %s\n", 
            this->ssid, this->password, this->remote_IP, this->local_IP);
    this->write_string(String(log_c_buffer));
}


size_t UDP_Communicator :: write(uint8_t n)  {
    this->udp->beginPacket(this->remote_IP, COMMUNICATE_PORT);
    this->udp->write(n);
    this->udp->endPacket();
    return 1;
}

size_t UDP_Communicator :: write(const uint8_t *buffer, size_t size)  {
    this->udp->beginPacket(this->remote_IP, COMMUNICATE_PORT);
    size_t n = 0;
    while(size--) {
        n += this->udp->write(*buffer++);
    }
    this->udp->endPacket();
    return n;
}

char* UDP_Communicator :: get_ssid(){
    return this->ssid;
}

char* UDP_Communicator :: get_password(){
    return this->password;
}

char* UDP_Communicator :: get_remote_ip(){
    return this->remote_IP;
}

#endif